package com.zz.framework.core.base

import android.text.TextUtils
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zz.framework.core.common.Clazz
import com.zz.framework.core.entity.ErrorCode
import com.zz.framework.core.entity.HttpResponse
import com.zz.framework.core.entity.HttpStatus
import com.zz.framework.core.ext.getString
import com.zz.framework.R
import kotlinx.coroutines.*
import retrofit2.HttpException

/**
 * Copyright © 2019 zanezhao All rights reserved.
 * Created by zanezhao on 2019-11-13 15:40.
 *
 * @author: zanezhao 1820022519@qq.com
 * @version: V1.0
 */
open class BaseViewModel<M> : ViewModel(), LifecycleObserver {
    // 通过反射注入 repository
    val repository: M by lazy { Clazz.getClass<M>(this).newInstance() }
    val errorLiveData: MutableLiveData<HttpStatus> = MutableLiveData()
    val finishedLiveData: MutableLiveData<Boolean> = MutableLiveData()

    private suspend fun tryCatch(
        tryBlock: suspend CoroutineScope.() -> Unit,
        catchBlock: suspend CoroutineScope.(Throwable) -> Unit,
        finallyBlock: suspend CoroutineScope.() -> Unit,
        handleCancellationExceptionManually: Boolean = false
    ) {
        coroutineScope {
            try {
                tryBlock()
            } catch (e: Throwable) {
                if (e !is CancellationException || handleCancellationExceptionManually) {
                    sendError(HttpStatus(500, e.message ?: ""))
                    catchBlock(e)
                } else {
                    e.printStackTrace()
                    throw e
                }
            } finally {
                finallyBlock()
            }
        }
    }


    fun launchUI(
        block: suspend CoroutineScope.() -> Unit,
        error: ((Throwable) -> Unit)? = null
    ): Job {
        return viewModelScope.launch(Dispatchers.Main) {
            tryCatch({
                block()
            }, {
                error?.invoke(it)
                it.printStackTrace()
            }, {}, true)
        }
    }

    fun <M> quickLaunch(block: Execute<M>.() -> Unit) {
        Execute<M>().apply(block)
    }


    inner class Execute<R> {
        private var startBlock: (() -> Unit)? = null
        private var successBlock: ((R?) -> Unit)? = null
        private var successRspBlock: ((HttpResponse<R>) -> Unit)? = null
        private var failBlock: ((HttpStatus) -> Unit) = { sendError(it) }
        private var exceptionBlock: ((Throwable) -> Unit)? = {
            sendError(it)
        }
        private var finallyBlock: (() -> Unit)? = { sendFinish() }

        fun onStart(block: () -> Unit) {
            this.startBlock = block
        }

        fun request(block: suspend CoroutineScope.() -> HttpResponse<R>?) {
            startBlock?.invoke()
            launchUI({
                successBlock?.let {
                    block()?.execute(successBlock, failBlock)
                } ?: block()?.executeRsp(successRspBlock, failBlock)
                finallyBlock?.invoke()
            }, exceptionBlock)
        }
        fun requestOriginal(block: suspend CoroutineScope.() -> R?) {
            startBlock?.invoke()
            launchUI({
                successBlock?.let {
                    block()?.let {
                        successBlock?.invoke(it)
                    }?:let {
                        sendError( HttpStatus(ErrorCode.EXCEPTION_ERROR, getString(com.zz.framework.R.string.text_network_error)))
                    }
                }
                finallyBlock?.invoke()
            }, exceptionBlock)
        }

        fun onSuccess(block: (R?) -> Unit) {
            this.successBlock = block
        }

        fun onSuccessRsp(block: (HttpResponse<R>) -> Unit) {
            this.successRspBlock = block
        }

        fun onFail(block: (HttpStatus) -> Unit) {
            this.failBlock = block
        }

        fun onException(block: (Throwable) -> Unit) {
            this.exceptionBlock = block
        }

        fun onFinally(block: () -> Unit) {
            this.finallyBlock = block
        }
    }

    protected fun sendError(r: HttpStatus) {
        sendFinish()
        if (r == null) return
        errorLiveData.postValue(r)
    }

    fun sendError(t: Throwable) {
        errorLiveData.postValue(getError(t))
        sendFinish()
    }

    fun sendFinish() {
        finishedLiveData.postValue(true)
    }

    fun getError(t: Throwable): HttpStatus {
        if ( t is HttpException){
            val msg =  if (TextUtils.isEmpty(t.localizedMessage)) t.message() else t.localizedMessage
            return HttpStatus(ErrorCode.EXCEPTION_ERROR, if (TextUtils.isEmpty(msg)) getString(R.string.text_network_error) else msg!!)
        }else {
            return HttpStatus(ErrorCode.EXCEPTION_ERROR, getString(R.string.text_network_error))
        }
    }

    fun sendError(msg:String? =null){
        errorLiveData.postValue(HttpStatus(ErrorCode.EXCEPTION_ERROR, if (TextUtils.isEmpty(msg)) getString(R.string.text_network_error) else msg!!))
        sendFinish()
    }

}